﻿// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SnapObjects.Data
{
    internal class SqlBuilderLoader<TModel> : ISqlBuilderLoader<TModel>
    {
        private readonly DataContext _dataContext;
        private readonly ModelSelectBuilder _modelSelectBuilder;
        private readonly ISqlQueryBuilder _queryBuilder;

        public SqlBuilderLoader(DataContext dataContext, ISqlQueryBuilder queryBuilder)
        {
            _dataContext = dataContext;

            _modelSelectBuilder = new ModelSelectBuilder(dataContext);

            _queryBuilder = queryBuilder;
        }

        public ILoadable<TModel> Load(params object[] parameters)
        {
            var adoDbParameters = this.GetAdoDbParmeters(parameters);

            var models = new ModelSqlExecutor(_dataContext).Select<TModel>(
                        _queryBuilder.ToSqlString(_dataContext),
                        adoDbParameters);

            return new ModelLoadable<TModel>(
                _dataContext, _modelSelectBuilder, models, adoDbParameters.ToList());
        }

        public Task<ILoadable<TModel>> LoadAsync(CancellationToken cancellationToken)
        {
            return this.LoadAsync(new object[] { }, cancellationToken);
        }

        public Task<ILoadable<TModel>> LoadAsync(params object[] parameters)
        {
            return this.LoadAsync(parameters, default);
        }

        public async Task<ILoadable<TModel>> LoadAsync(object[] parameters, CancellationToken cancellationToken)
        {
            var adoDbParameters = this.GetAdoDbParmeters(parameters);

            var executor = new ModelSqlExecutor(_dataContext);
            
            var models = await executor.SelectAsync<TModel>(
                _queryBuilder.ToSqlString(_dataContext),
                adoDbParameters, 
                cancellationToken);

            return new ModelLoadable<TModel>(
                _dataContext, _modelSelectBuilder, models, adoDbParameters.ToList());
        }

        public ILoadable<TModel> LoadByPage(int currentIndex, int pageSize, params object[] parameters)
        {
            var adoDbParameters = this.GetAdoDbParmeters(parameters);

            var executor = new ModelSqlExecutor(_dataContext);
            
            var models = executor.SelectPage<TModel>(
                        _queryBuilder.ToSqlString(_dataContext),
                        currentIndex,
                        pageSize,
                        adoDbParameters);

            return new ModelLoadable<TModel>(
                _dataContext, _modelSelectBuilder, models, adoDbParameters.ToList());
        }

        public Task<ILoadable<TModel>> LoadByPageAsync(
            int currentIndex, int pageSize, CancellationToken cancellationToken)
        {
            return this.LoadByPageAsync(currentIndex, pageSize, new object[] { }, cancellationToken);
        }

        public Task<ILoadable<TModel>> LoadByPageAsync(
            int currentIndex, int pageSize, params object[] parameters)
        {
            return this.LoadByPageAsync(currentIndex, pageSize, parameters, default);
        }

        public async Task<ILoadable<TModel>> LoadByPageAsync(
            int currentIndex, int pageSize, object[] parameters, CancellationToken cancellationToken)
        {
            var adoDbParameters = this.GetAdoDbParmeters(parameters);

            var executor = new ModelSqlExecutor(_dataContext);

            var models = await executor.SelectPageAsync<TModel>(
                _queryBuilder.ToSqlString(_dataContext),
                currentIndex,
                pageSize,
                adoDbParameters,
                cancellationToken);

            return new ModelLoadable<TModel>(
              _dataContext, _modelSelectBuilder, models, adoDbParameters.ToList());
        }

        private IList<IAdoDbParameter> GetAdoDbParmeters(object[] parameters)
        {
            IList<IAdoDbParameter> adoDbParameters = new List<IAdoDbParameter>();

            if (parameters == null || parameters.Length < 1)
            {
                return adoDbParameters;
            }

            var builderBase = (SqlBuilderBase)_queryBuilder;

            IReadOnlyList<ISqlParameter> definedParameters;

            if (parameters.Length == builderBase.BuilderParameters.Length)
            {
                definedParameters = builderBase.BuilderParameters;
            }
            else
            {
                //To get used parameters
                _queryBuilder.ToSqlString(_dataContext);

                definedParameters = builderBase.UsedParameters.Values.ToList();
            }

            adoDbParameters = ModelParameterHelper.BindParameters(
                _dataContext,
                definedParameters,
                parameters);

            return adoDbParameters;
        }
    }
}
